package cn.huaqingcheng.tool.http.impl;

import cn.huaqingcheng.tool.core.StrTool;
import cn.huaqingcheng.tool.http.ContentType;
import cn.huaqingcheng.tool.http.HttpHeader;
import cn.huaqingcheng.tool.http.HttpHeaders;
import cn.huaqingcheng.tool.http.HttpRequest;
import cn.huaqingcheng.tool.http.HttpResponse;
import cn.huaqingcheng.tool.http.HttpVersion;
import cn.huaqingcheng.tool.http.spi.HttpClient;
import cn.huaqingcheng.tool.json.JsonTool;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.CookieManager;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.time.Duration;

import static java.net.http.HttpRequest.BodyPublisher;
import static java.net.http.HttpRequest.BodyPublishers;
import static java.net.http.HttpRequest.Builder;
import static java.net.http.HttpRequest.newBuilder;
import static java.net.http.HttpResponse.BodyHandlers;

/**
 * HTTP 客户端实现
 * <p>
 * 实现是线程安全的
 **/
@Slf4j
public class HttpClientImpl implements HttpClient {

    public static final Charset UTF_8 = StandardCharsets.UTF_8;

    private final java.net.http.HttpClient httpClient = java.net.http.HttpClient.newBuilder()
        .version(java.net.http.HttpClient.Version.HTTP_2)
        .connectTimeout(Duration.ofSeconds(30))
        .cookieHandler(new CookieManager())
        .followRedirects(java.net.http.HttpClient.Redirect.NORMAL)
        .priority(256)
        .build();

    /**
     * {@link HttpRequest} 转为 {@link java.net.http.HttpRequest}
     * <p>
     * 请求体格式处理
     */
    private static java.net.http.HttpRequest convert(HttpRequest request) throws FileNotFoundException {
        Builder builder = newBuilder()
            .uri(URI.create(request.url()));

        // 设置 header
        request.header().forEach(builder::setHeader);

        // 设置 body
        BodyPublisher bodyPublisher = switch (request.method()) {
            case POST, PUT, DELETE -> {
                Object body = request.body();
                if (body instanceof String str) {
                    yield BodyPublishers.ofString(str, StandardCharsets.UTF_8);
                }
                if (body instanceof File file) {
                    yield BodyPublishers.ofFile(file.toPath());
                }
                if (body instanceof Path path) {
                    yield BodyPublishers.ofFile(path);
                }
                if (body instanceof InputStream is) {
                    yield BodyPublishers.ofInputStream(() -> is);
                }

                String ct = request.header().get(HttpHeader.CONTENT_TYPE);
                if (StrTool.isEmpty(ct) || ct.contains(ContentType.JSON.value())) {
                    yield BodyPublishers.ofString(JsonTool.toJson(body), UTF_8);
                }

                log.warn("无法识别的 body 类型, 请求体被忽略");
                yield BodyPublishers.noBody();
            }
            default -> BodyPublishers.noBody();
        };

        // 设置 method
        builder.method(request.method().toString(), bodyPublisher)
            .timeout(request.timeout());

        return builder.build();
    }

    /**
     * 执行模板
     */
    private <T> java.net.http.HttpResponse<T> execTemplate(HttpRequest option, java.net.http.HttpResponse.BodyHandler<T> handler) {
        try {
            return httpClient.send(convert(option), handler);
        } catch (IOException | InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 执行 HTTP 请求
     *
     * @param option 请求
     * @return 响应
     */
    @Override
    public HttpResponse<String> exec(HttpRequest option) {
        long start = System.currentTimeMillis();
        if (log.isTraceEnabled()) {
            log.trace("{}", option.toString());
        } else if (log.isDebugEnabled()) {
            log.debug("{}", option.toString(true, false));
        } else if (log.isInfoEnabled()) {
            log.info("{}", option.toString(false, false));
        }

        java.net.http.HttpResponse<String> res = execTemplate(option, BodyHandlers.ofString(UTF_8));
        HttpResponse<String> response = convert(res);
        if (log.isTraceEnabled()) {
            log.trace("{} {} ms", response.toString(), System.currentTimeMillis() - start);
        } else if (log.isDebugEnabled()) {
            log.debug("{} {} ms", response.toString(true, false), System.currentTimeMillis() - start);
        } else if (log.isInfoEnabled()) {
            log.info("{} {} ms", response.toString(false, false), System.currentTimeMillis() - start);
        }
        return response;
    }

    /**
     * {@link java.net.http.HttpRequest} 转为 {@link HttpResponse}
     */
    private static <T> HttpResponse<T> convert(java.net.http.HttpResponse<T> res) {
        return new HttpResponse<T>()
            .version(HttpVersion.of(res.version()))
            .body(res.body())
            .status(res.statusCode())
            .statusMsg(String.valueOf(res.statusCode()))
            .header(new HttpHeaders(res.headers()));
    }

    /**
     * 执行下载请求, 理论上来说 request 应该是 GET 但是不做限制
     *
     * @param request 请求
     * @param file    下载到目标文件
     */
    @Override
    public HttpResponse<Path> exec(HttpRequest request, File file) {
        java.net.http.HttpResponse.BodyHandler<Path> handler = BodyHandlers.ofFileDownload(
            file.toPath(),
            StandardOpenOption.WRITE,
            StandardOpenOption.READ,
            StandardOpenOption.CREATE
        );

        java.net.http.HttpResponse<Path> response = execTemplate(request, handler);

        return convert(response);
    }

}
